<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8" />
  <title>原型与原型链</title>
</head>

<body>
  <script>
    /**
     * 1.函数的prototype属性(图)
     *   每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
     *   原型对象中有一个属性constructor, 它指向函数对象
     * 2.给原型对象添加属性(一般都是方法)
     *    作用: 函数的所有实例对象自动拥有原型中的属性(方法)
     * */
    console.log(Date.prototype, typeof Date.prototype);

    //每个函数都有一个prototype属性, 它默认指向一个Object空对象(即称为: 原型对象)
    function Fun() { }
    //向fun原型中添加test方法
    Fun.prototype.test = function () {
      console.log("test()");
    };
    console.log(Fun.prototype, '====');

    //原型对象中有一个属性constructor, 它指向函数对象
    console.log(Date.prototype.constructor === Date); //true
    console.log(Fun.prototype.constructor === Fun); //true

    //给原型对象添加属性(一般式方法)  ====》 实例对象可以访问
    var fun = new Fun();
    fun.test();

    /**
     * 1.显式原型与隐式原型
     *   每个函数function都有一个prototype，即显式原型(属性)
     *   每个实例对象都有一个__proto__，可称为隐式原型(属性)
     * 2.实例对象的隐式原型等于构造函数的显式原型
     * 3.内存结构图(见原型--显式与隐式原型图)
     * 4.总结：
     *   函数的prototype属性：在定义函数时自动添加的，默认值是一个空Object对象
     *   对象的__proto__属性：创建对象时自动添加的，默认值为构造函数的prototype属性值
     * */
    //每个函数function都有一个prototype，即显式原型(属性)
    //定义构造函数
    function Fn() {
      //内部语句： this.prototype = {}
    }
    console.log(Fn.prototype); // prototype函数创建时添加

    //每个实例对象都有一个__proto__，可称为隐式原型(属性)
    //创建实例对象
    var fn = new Fn(); //内部语句：this.__proto__ = Fn.prototype
    console.log(fn.__proto__); //实例创建添加

    //2.对象的隐式原型的值为其对应构造函数的显式原型的值
    console.log(Fn.prototype === fn.__proto__); //true

    //给原型添加方法
    Fn.prototype.test = function () {
      console.log("test()");
    };
    //通过实例调用原型的方法
    fn.test();

    /**
     * 参考博客：https://juejin.cn/post/6844903875216932877
     *
     * 1.原型链(图解)
     *   访问一个对象的属性时，先在自身属性中查找，若找到则返回；
     *   若没有找到，再沿着__proto__(隐士原型链)链向上查找，找到返回；
     *    一直找到Object原型对象，找到返回，找不到返回undefined;
     * 2.作用：查找对象的属性(方法)
     * 3.本质：始终沿着(__proto__)隐式原型链查找
     * */
    //创建函数
    function Fnn() {
      this.test1 = function () {
        console.log("test1()");
      };
    }

    Fnn.prototype.test2 = function () {
      console.log("test2()");
    };

    var fnn = new Fnn();

    fnn.test1();
    fnn.test2();
    console.log(fnn.toString());
    console.log(fnn.test3); //undefined

    /**
     * 1.函数的显式原型指向的是对象默认的空Object实例对象
     * 2.Function是通过new自己产生的实例
     * 3.instanceof是如何判断的?
     *   表达式：A instanceof B
     *   如果B函数的显示原型对象在A对象的原型链上，返回true，否则返回false
     * */
    console.log(Fn.prototype instanceof Object); //true
    console.log(Object.prototype instanceof Object); //false
    console.log(Function.prototype instanceof Object); //true
    /**
     * 2.所有函数都是Function的实例(包含Function)
     * */
    console.log(Function.__proto__ === Function.prototype); //true
    /**
     * 3.Object的原型对象是原型链的尽头，找到尽头就返回null
     * */
    console.log(Object.prototype.__proto__ === null); //true

    /**
     * 原型链属性问题：[属性放在对象上，方法存在原型中]
     *  读取对象的属性值时：会自动到原型链中查找
     *  设置对象的属性值时：不会查找原型链，如果当前对象中没有此属性，直接添加此属性并设置其值
     *  方法一般定义在原型中，属性一般通过构造函数定义在对象本身上
     * */
    function Fn2() { }
    Fn2.prototype.a = "xxx";
    var fn2 = new Fn2();
    console.log(fn2);
    console.log(fn2.a);

    var fn3 = new Fn2();
    fn3.a = "yyy";
    console.log(fn3.a, fn2.a);

    //属性放在对象上，方法存在原型中
    function Person(name, age) {
      this.name = name;
      this.age = age;
    }
    Person.prototype.setName = function (name) {
      this.name = name;
    };
    var p1 = new Person("Tom", 18);
    p1.setName("Bob");
    console.log(p1);

    var p2 = new Person("Jack", 12);
    p2.setName("Cat");
    console.log(p2);

    console.log(p1.__proto__ === p2.__proto__); //true

    //原型的作用： 扩展对象的方法或属性
    let date = new Date();
    Date.prototype.formate = function () {
      //this指向Date
      let year = this.getFullYear();
      let month = this.getMonth() + 1;
      let day = this.getDay();
      return `${year}年${month}月${day}日`;
    };
    console.log(date.formate(), '=====');
  </script>
</body>

</html>